package com.crunch.math;

import java.util.Arrays;

/**
 * A class for 2D vectors.
 */
public final class Vector2f {
	float[] components;

	/**
	 * Constructs a vector with all components set to 0.
	 */
	public Vector2f() {
		components = new float[] { 0.0f, 0.0f };
	}

	/**
	 * Sets all components to 0.
	 */
	public void setZero() {
		Arrays.fill(components, 0.0f);
	}

	/**
	 * Returns whether all components are 0.
	 */
	public boolean isZero() {
		return components[0] == 0.0f && components[1] == 0.0f;
	}

	/**
	 * Constructs a vector by copying the one provided.
	 */
	public Vector2f(Vector2f v) {
		components = Arrays.copyOf(v.components, v.components.length);
	}

	/**
	 * Sets the vector to the provided components in the vector {@code v}.
	 */
	public void set(Vector2f v) {
		System.arraycopy(v.components, 0, components, 0, components.length);
	}

	/**
	 * Constructs a vector with the provided components.
	 */
	public Vector2f(float x, float y) {
		components = new float[] { x, y };
	}

	/**
	 * Sets the vector to the provided components.
	 */
	public void set(float x, float y) {
		components[0] = x;
		components[1] = y;
	}

	/**
	 * Returns the <i>i</i>th component.
	 */
	public float get(int i) {
		return components[i];
	}

	/**
	 * Sets the <i>i</i>th component.
	 */
	public void set(int i, float s) {
		components[i] = s;
	}

	/**
	 * Returns the x-component.
	 */
	public float x() {
		return components[0];
	}

	/**
	 * Returns the y-component.
	 */
	public float y() {
		return components[1];
	}

	/**
	 * Sets the x-component.
	 */
	public void x(float s) {
		components[0] = s;
	}

	/**
	 * Sets the y-component.
	 */
	public void y(float s) {
		components[1] = s;
	}

	/**
	 * Returns the sum of this vector and {@code rhs}.
	 */
	public Vector2f add(Vector2f rhs) {
		return new Vector2f(this).addAssign(rhs);
	}

	/**
	 * Adds {@code rhs} to this vector.
	 */
	public Vector2f addAssign(Vector2f rhs) {
		for (int i = 0; i < components.length; ++i) {
			components[i] += rhs.components[i];
		}
		return this;
	}

	/**
	 * Returns the difference between this vector and {@code rhs}.
	 */
	public Vector2f subtract(Vector2f rhs) {
		return new Vector2f(this).subtractAssign(rhs);
	}

	/**
	 * Subtracts {@code rhs} from this vector.
	 */
	public Vector2f subtractAssign(Vector2f rhs) {
		for (int i = 0; i < components.length; ++i) {
			components[i] -= rhs.components[i];
		}
		return this;
	}

	/**
	 * Returns this vector negated.
	 */
	public Vector2f negate() {
		return new Vector2f(this).negateAssign();
	}

	/**
	 * Negates this vector.
	 */
	public Vector2f negateAssign() {
		for (int i = 0; i < components.length; ++i) {
			components[i] = -components[i];
		}
		return this;
	}

	/**
	 * Returns the product of this vector and {@code rhs}.
	 */
	public Vector2f multiply(float rhs) {
		return new Vector2f(this).multiplyAssign(rhs);
	}

	/**
	 * Multiplies this vector by {@code rhs}.
	 */
	public Vector2f multiplyAssign(float rhs) {
		for (int i = 0; i < components.length; ++i) {
			components[i] *= rhs;
		}
		return this;
	}

	/**
	 * Returns the quotient of this vector and {@code rhs}.
	 */
	public Vector2f divide(float rhs) {
		return new Vector2f(this).divideAssign(rhs);
	}

	/**
	 * Divides this vector by {@code rhs}.
	 */
	public Vector2f divideAssign(float rhs) {
		for (int i = 0; i < components.length; ++i) {
			components[i] /= rhs;
		}
		return this;
	}

	/**
	 * Returns the component-wise product of this vector and {@code rhs}.
	 */
	public Vector2f multiply(Vector2f rhs) {
		return new Vector2f(this).multiplyAssign(rhs);
	}

	/**
	 * Multiplies this vector by {@code rhs} component-wise.
	 */
	public Vector2f multiplyAssign(Vector2f rhs) {
		for (int i = 0; i < components.length; ++i) {
			components[i] *= rhs.components[i];
		}
		return this;
	}

	/**
	 * Returns the component-wise quotient of this vector and {@code rhs}.
	 */
	public Vector2f divide(Vector2f rhs) {
		return new Vector2f(this).divideAssign(rhs);
	}

	/**
	 * Divides this vector by {@code rhs} component-wise.
	 */
	public Vector2f divideAssign(Vector2f rhs) {
		for (int i = 0; i < components.length; ++i) {
			components[i] /= rhs.components[i];
		}
		return this;
	}

	/**
	 * Returns whether this vector is equal to the object {@code o}.
	 */
	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		} else if (o == null || !(o instanceof Vector2f)) {
			return false;
		} else {
			return equals((Vector2f) o);
		}
	}

	/**
	 * Returns a hash code for this object.
	 */
	@Override
	public int hashCode() {
		int bits = 0;
		for (int i = 0; i < components.length; ++i) {
			bits ^= Float.floatToIntBits(components[i]);
		}
		return bits;
	}

	/**
	 * Returns whether this vector is equal to the vector {@code rhs}.
	 */
	public boolean equals(Vector2f rhs) {
		return Arrays.equals(components, rhs.components);
	}

	/**
	 * Returns this vector normalized.
	 */
	public Vector2f normalize() {
		return new Vector2f(this).normalizeAssign();
	}

	/**
	 * Normalizes this vector.
	 */
	public Vector2f normalizeAssign() {
		float mag2 = magnitudeSquared();
		if (mag2 == 0.0f) {
			setZero();
		} else {
			float invMag = 1.0f / (float) Math.sqrt(mag2);
			multiplyAssign(invMag);
		}
		return this;
	}

	/**
	 * Returns this vector rotated by 90 degrees counterclockwise.
	 */
	public Vector2f rotateCcw() {
		return new Vector2f(this).rotateCcwAssign();
	}

	/**
	 * Rotates this vector by 90 degrees counterclockwise.
	 */
	public Vector2f rotateCcwAssign() {
		set(-components[1], components[0]);
		return this;
	}

	/**
	 * Returns this vector rotated by 90 degrees clockwise.
	 */
	public Vector2f rotateCw() {
		return new Vector2f(this).rotateCwAssign();
	}

	/**
	 * Rotates this vector by 90 degrees clockwise.
	 */
	public Vector2f rotateCwAssign() {
		set(components[1], -components[0]);
		return this;
	}

	/**
	 * Returns the magnitude of this vector.
	 */
	public float magnitude() {
		return (float) Math.sqrt(magnitudeSquared());
	}

	/**
	 * Returns the squared magnitude of this vector.
	 */
	public float magnitudeSquared() {
		return components[0]*components[0] + components[1]*components[1];
	}

	/**
	 * Returns the raw underlying mutable array.
	 */
	public float[] getArray() {
		return components;
	}
}